/**
 ********************************************************************
 * @author roocket
 * @file    file_crypt.c
 * @version V0.0.0
 * @date    2024/3/22
 * @brief   This file is to indicate file cryptogram functions. 
 * @attention Code file rules:
 * rule: file encoding use UTF8;
 * rule: max line length 120 characters;
 * rule: line separator \r\n;
 * rule: use clion auto code format tool.
 */

#include "file_crypt.h"

static void _file_crypt_encrypt_init(EVP_CIPHER_CTX *ctx, const char *passwd)
{
    const int SALT_SIZE = 8;
    const unsigned char *salt = FILE_SALT;
    unsigned char key[EVP_MAX_KEY_LENGTH], iv[EVP_MAX_IV_LENGTH];

    const EVP_CIPHER *cipher = EVP_aes_256_cbc();

    EVP_BytesToKey(cipher, EVP_sha256(), 
        salt, (const unsigned char *)passwd, 
        strlen(passwd), 1, key, iv);

    /* init ex of encryption */
    EVP_EncryptInit_ex(ctx, cipher, NULL, key, iv);
    return;
}

int file_crypt_encrypt_data(const unsigned char *be_enc_data, const int be_enc_len, unsigned char **enc_data, int *encLen, const char *passwd)
{
    int rv;
    int update_len;
    int final_len;

    /* init the ctx for cryptogram */
    EVP_CIPHER_CTX *ctx = EVP_CIPHER_CTX_new();
    _file_crypt_encrypt_init(ctx, passwd);

    *enc_data = (unsigned char *)malloc(be_enc_len);
    if (NULL == enc_data)
        goto fail_mm;

    *encLen = 0;

    rv = EVP_EncryptUpdate(ctx, *enc_data, &update_len, be_enc_data, be_enc_len);
    if (1 != rv)
        goto fail_mm;

    rv = EVP_EncryptFinal_ex(ctx, *enc_data + update_len, &final_len);
    if (1 != rv)
        goto fail_mm;

    *encLen = update_len + final_len;
    EVP_CIPHER_CTX_reset(ctx);
    return 0;

fail_mm:
    if (ctx)
    EVP_CIPHER_CTX_reset(ctx);
    /* it must free out of function */
    return -1;
}

int file_crypt_encrypt_file(const char *be_enc_file, const char *enc_file, const char *passwd, double *t)
{
    unsigned char outs[FILE_CRYPTOGRAM_BUFFER_LEN];
    unsigned char ins[FILE_CRYPTOGRAM_BUFFER_LEN];
    int outl;
    int inl;
    int rv;
    int i;
    FILE *in = NULL;
    FILE *out = NULL;
    int err = -1;
    clock_t st, et;

    st = clock();

    /* Init the encryption */
    EVP_CIPHER_CTX *ctx = EVP_CIPHER_CTX_new();
    _file_crypt_encrypt_init(ctx, passwd);

    /* handler for file */
    in = fopen(be_enc_file, "rb");
    if (NULL == in)
        goto fail_clean_encrypte;

    out = fopen(enc_file, "wb");
    if (NULL == out)
        goto fail_clean_encrypte;

    /* encrypte and update content by each page */
    while (!feof(in))
    {
        inl = fread(ins, 1, FILE_CRYPTOGRAM_BUFFER_LEN, in);

        rv = EVP_EncryptUpdate(ctx, outs, &outl, ins, inl);
        if (1 != rv)
            goto fail_clean_encrypte;

        fwrite(outs, 1, outl, out);
    }

    rv = EVP_EncryptFinal_ex(ctx, outs, &outl);
    if (1 != rv)
        goto fail_clean_encrypte;

    fwrite(outs, 1, outl, out);

    /* clock the time */
    et = clock();
    *t = (double)(et - st) / CLOCKS_PER_SEC;
    err = 0;

fail_clean_encrypte:
    if (in)
        fclose(in);
    if (out)
        fclose(out);
    if (ctx)
        EVP_CIPHER_CTX_reset(ctx);
    return err;
}

static void _file_crypt_decrypt_init(EVP_CIPHER_CTX *ctx, const char *passwd)
{
    const int SALT_SIZE = 8;
    const unsigned char *salt = FILE_SALT;
    unsigned char key[EVP_MAX_KEY_LENGTH], iv[EVP_MAX_IV_LENGTH];

    const EVP_CIPHER *cipher = EVP_aes_256_cbc();

    EVP_BytesToKey(cipher, EVP_sha256(), salt, (const unsigned char *)passwd, strlen(passwd), 1, key, iv);

    /* ex for decryption */
    EVP_DecryptInit_ex(ctx, cipher, NULL, key, iv);
}
 
int file_crypt_decrypt_data(const unsigned char *be_dec_data, const int be_dec_len, unsigned char **dec_data, int *dec_len, const char *passwd)
{
    int rv;
    int update_len;
    int final_len;

    EVP_CIPHER_CTX *ctx = EVP_CIPHER_CTX_new();
    _file_crypt_decrypt_init(ctx, passwd);

    *dec_data = (unsigned char *)malloc(be_dec_len);
    if (NULL == dec_data)
        goto fail_mm;

    *dec_len = 0;

    rv = EVP_DecryptUpdate(ctx, *dec_data, &update_len, be_dec_data, be_dec_len);
    if (1 != rv)
        goto fail_mm;

    rv = EVP_DecryptFinal_ex(ctx, *dec_data + update_len, &final_len);
    if (1 != rv)
        goto fail_mm;

    *dec_len = update_len + final_len;
    EVP_CIPHER_CTX_reset(ctx);
    return 0;

fail_mm:
    if (ctx)
        EVP_CIPHER_CTX_reset(ctx);
    return -1;
}

int file_crypt_decrypt_file(const char *be_dec_file,  const char *dec_file, const char *passwd, double *t)
{
    unsigned char outs[FILE_CRYPTOGRAM_BUFFER_LEN + EVP_MAX_KEY_LENGTH]; 
    int outl;
    unsigned char ins[FILE_CRYPTOGRAM_BUFFER_LEN];
    int inl;
    int rv;
    int i;
    FILE *in = NULL;
    FILE *out = NULL;
    int err = -1;
    clock_t st, et;

    st = clock();

    EVP_CIPHER_CTX *ctx = EVP_CIPHER_CTX_new();
    _file_crypt_decrypt_init(ctx, passwd);

    in = fopen(be_dec_file, "rb");
    if (NULL == in)
        goto fail_clean_dec;

    out = fopen(dec_file, "wb");
    if (NULL == out)
        goto fail_clean_dec;

    while (!feof(in))
    {
        inl = fread(ins, 1, FILE_CRYPTOGRAM_BUFFER_LEN, in);

        rv = EVP_DecryptUpdate(ctx, outs, &outl, ins, inl);
        if (1 != rv)
            goto fail_clean_dec;

        fwrite(outs, 1, outl, out);
    }

    rv = EVP_DecryptFinal_ex(ctx, outs, &outl);
    if (1 != rv)
        goto fail_clean_dec;

    fwrite(outs, 1, outl, out);

    /* clock the time */
    et = clock();
    *t = (double)(et - st) / CLOCKS_PER_SEC;

    err = 0;

fail_clean_dec:
    if (in)
        fclose(in);
    if (out)
        fclose(out);
    if (ctx)
        EVP_CIPHER_CTX_reset(ctx);
    return err;
}